package com.gzc.just.play.last.war.common.connection;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Server connection manager
 * Manages TCP connections between servers, providing connection pools, heartbeat detection and reconnection functionality
 */
@Component
public class ServerConnectionManager {
    
    private static final Logger log = LoggerFactory.getLogger(ServerConnectionManager.class);
    
    // Connection pool management
    private final ConcurrentHashMap<ServerType, ConnectionPool> connectionPools = new ConcurrentHashMap<>();
    
    // Heartbeat detection
    private final ScheduledExecutorService heartbeatExecutor = Executors.newScheduledThreadPool(2);
    
    // Connection status monitoring
    private final ConcurrentHashMap<ServerType, AtomicBoolean> connectionStatus = new ConcurrentHashMap<>();
    
    // Server configurations
    private final Map<ServerType, ServerConfig> serverConfigs = new ConcurrentHashMap<>();
    
    // Netty event loop group
    private final EventLoopGroup workerGroup = new NioEventLoopGroup();
    
    /**
     * Server type enumeration
     */
    public enum ServerType {
        GATE_SERVER("gate", 8002),
        LOGIN_SERVER("login", 8003),
        BALANCE_SERVER("balance", 8004),
        CENTRAL_SERVER("central", 8001),
        SCENE_SERVER("scene", 8005);
        
        private final String name;
        private final int defaultPort;
        
        ServerType(String name, int defaultPort) {
            this.name = name;
            this.defaultPort = defaultPort;
        }
        
        public String getName() {
            return name;
        }
        
        public int getDefaultPort() {
            return defaultPort;
        }
    }
    
    /**
     * Server configuration
     */
    public static class ServerConfig {
        private final String host;
        private final int port;
        private final int retryInterval;
        private final int maxRetries;
        
        public ServerConfig(String host, int port) {
            this(host, port, 5000, 3);
        }
        
        public ServerConfig(String host, int port, int retryInterval, int maxRetries) {
            this.host = host;
            this.port = port;
            this.retryInterval = retryInterval;
            this.maxRetries = maxRetries;
        }
        
        public String getHost() {
            return host;
        }
        
        public int getPort() {
            return port;
        }
        
        public int getRetryInterval() {
            return retryInterval;
        }
        
        public int getMaxRetries() {
            return maxRetries;
        }
    }
    
    /**
     * Connection pool
     */
    public static class ConnectionPool {
        private final ConcurrentLinkedQueue<Channel> connections = new ConcurrentLinkedQueue<>();
        private final AtomicInteger activeConnections = new AtomicInteger(0);
        private final int maxConnections;
        
        public ConnectionPool(int maxConnections) {
            this.maxConnections = maxConnections;
        }
        
        public Channel getConnection() {
            // Remove closed connections
            connections.removeIf(channel -> !channel.isActive());
            return connections.poll();
        }
        
        public boolean addConnection(Channel channel) {
            if (activeConnections.get() >= maxConnections) {
                return false;
            }
            connections.offer(channel);
            activeConnections.incrementAndGet();
            return true;
        }
        
        public void removeConnection(Channel channel) {
            connections.remove(channel);
            activeConnections.decrementAndGet();
        }
        
        public int getActiveConnections() {
            return activeConnections.get();
        }
        
        public void closeAll() {
            Channel channel;
            while ((channel = connections.poll()) != null) {
                if (channel.isOpen()) {
                    channel.close();
                }
                activeConnections.decrementAndGet();
            }
        }
    }
    
    /**
     * Register server configuration
     * @param serverType Server type
     * @param config Server configuration
     */
    public void registerServerConfig(ServerType serverType, ServerConfig config) {
        serverConfigs.put(serverType, config);
        connectionStatus.put(serverType, new AtomicBoolean(false));
        connectionPools.put(serverType, new ConnectionPool(10)); // Default max 10 connections
    }
    
    /**
     * Connect to server
     * @param serverType Server type
     * @return Connection Future
     */
    public CompletableFuture<Channel> connectToServer(ServerType serverType) {
        return connectToServer(serverType, serverConfigs.get(serverType));
    }
    
    /**
     * Connect to specified server
     * @param serverType Server type
     * @param config Server configuration
     * @return Connection Future
     */
    public CompletableFuture<Channel> connectToServer(ServerType serverType, ServerConfig config) {
        if (config == null) {
            return CompletableFuture.failedFuture(new IllegalArgumentException("Server config not found"));
        }
        
        CompletableFuture<Channel> future = new CompletableFuture<>();
        
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(workerGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ch.pipeline()
                                .addLast("frameDecoder", new MessageDecoder())
                                .addLast("frameEncoder", new MessageEncoder())
                                .addLast("handler", new ServerConnectionHandler(serverType));
                    }
                });
        
        // Attempt connection
        bootstrap.connect(config.getHost(), config.getPort())
                .addListener((ChannelFuture connectFuture) -> {
                    if (connectFuture.isSuccess()) {
                        Channel channel = connectFuture.channel();
                        ConnectionPool pool = connectionPools.get(serverType);
                        if (pool.addConnection(channel)) {
                            connectionStatus.get(serverType).set(true);
                            future.complete(channel);
                            log.info("Successfully connected to {} server at {}:{}", 
                                     serverType.getName(), config.getHost(), config.getPort());
                        } else {
                            channel.close();
                            future.completeExceptionally(new RuntimeException("Connection pool is full"));
                        }
                    } else {
                        connectionStatus.get(serverType).set(false);
                        future.completeExceptionally(connectFuture.cause());
                        log.error("Failed to connect to {} server at {}:{}, cause: {}", 
                                  serverType.getName(), config.getHost(), config.getPort(), connectFuture.cause().getMessage());
                    }
                });
        
        return future;
    }
    
    /**
     * Get server connection
     * @param serverType Server type
     * @return Server connection
     */
    public Channel getConnection(ServerType serverType) {
        ConnectionPool pool = connectionPools.get(serverType);
        if (pool == null) {
            return null;
        }
        
        Channel channel = pool.getConnection();
        if (channel == null || !channel.isActive()) {
            // Attempt to reconnect
            log.warn("No active connection to {} server, attempting to reconnect", serverType.getName());
            reconnect(serverType);
            return null;
        }
        
        return channel;
    }
    
    /**
     * Send message to a specific server type
     * @param serverType Target server type
     * @param msgId Message ID
     * @param msg Message body
     */
    public void sendMsg(ServerType serverType, int msgId, Object msg) {
        Channel channel = getConnection(serverType);
        if (channel != null && channel.isActive()) {
            try {
                com.gzc.just.play.last.war.common.net.NetUtils.sendMsg(channel, msgId, msg);
            } catch (Exception e) {
                log.error("Failed to send message {} to server {}", msgId, serverType.getName(), e);
            }
        } else {
            log.warn("Cannot send message {} to server {}, no active connection", msgId, serverType.getName());
        }
    }

    /**
     * Start heartbeat detection
     */
    public void startHeartbeat() {
        // Send heartbeat every 30 seconds
        heartbeatExecutor.scheduleAtFixedRate(() -> {
            for (Map.Entry<ServerType, AtomicBoolean> entry : connectionStatus.entrySet()) {
                ServerType serverType = entry.getKey();
                AtomicBoolean isConnected = entry.getValue();
                
                if (isConnected.get()) {
                    // Send heartbeat
                    sendHeartbeat(serverType);
                } else {
                    // Attempt reconnection
                    log.info("Server {} is disconnected, attempting to reconnect", serverType.getName());
                    reconnect(serverType);
                }
            }
        }, 5, 30, TimeUnit.SECONDS);
    }
    
    /**
     * Stop heartbeat detection
     */
    public void stopHeartbeat() {
        heartbeatExecutor.shutdown();
        try {
            if (!heartbeatExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
                heartbeatExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    
    /**
     * Send heartbeat
     * @param serverType Server type
     */
    private void sendHeartbeat(ServerType serverType) {
        Channel channel = getConnection(serverType);
        if (channel != null && channel.isActive()) {
            // Send heartbeat message
            HeartbeatMessage heartbeat = new HeartbeatMessage(System.currentTimeMillis());
            channel.writeAndFlush(heartbeat);
            log.debug("Sent heartbeat to {} server", serverType.getName());
        }
    }
    
    /**
     * Reconnect to server
     * @param serverType Server type
     */
    private void reconnect(ServerType serverType) {
        ServerConfig config = serverConfigs.get(serverType);
        if (config == null) {
            return;
        }
        
        connectToServer(serverType, config)
                .whenComplete((channel, throwable) -> {
                    if (throwable != null) {
                        log.error("Failed to reconnect to {} server, will retry in {} seconds", 
                                  serverType.getName(), config.getRetryInterval() / 1000);
                        // Schedule retry
                        heartbeatExecutor.schedule(() -> reconnect(serverType), 
                                               config.getRetryInterval(), TimeUnit.MILLISECONDS);
                    }
                });
    }
    
    /**
     * Handle disconnection
     * @param serverType Server type
     * @param channel Connection channel
     */
    public void handleDisconnection(ServerType serverType, Channel channel) {
        ConnectionPool pool = connectionPools.get(serverType);
        if (pool != null) {
            pool.removeConnection(channel);
        }
        
        // If no active connections, update status
        if (pool != null && pool.getActiveConnections() == 0) {
            connectionStatus.get(serverType).set(false);
            log.warn("All connections to {} server are lost", serverType.getName());
            
            // Schedule reconnection
            ServerConfig config = serverConfigs.get(serverType);
            if (config != null) {
                heartbeatExecutor.schedule(() -> reconnect(serverType), 
                                       config.getRetryInterval(), TimeUnit.MILLISECONDS);
            }
        }
    }
    
    /**
     * Check server connection status
     * @param serverType Server type
     * @return Whether connected
     */
    public boolean isConnected(ServerType serverType) {
        AtomicBoolean status = connectionStatus.get(serverType);
        return status != null && status.get();
    }
    
    /**
     * Get connection status
     * @return Server connection status map
     */
    public Map<ServerType, Boolean> getConnectionStatus() {
        Map<ServerType, Boolean> status = new ConcurrentHashMap<>();
        connectionStatus.forEach((serverType, atomicBoolean) -> 
            status.put(serverType, atomicBoolean.get()));
        return status;
    }
    
    /**
     * Close all connections
     */
    public void shutdown() {
        log.info("Shutting down server connection manager...");
        
        // Close heartbeat thread pool
        heartbeatExecutor.shutdown();
        
        // Close all connections
        connectionPools.values().forEach(pool -> {
            pool.connections.forEach(Channel::close);
        });
        
        // Close Netty event loop group
        workerGroup.shutdownGracefully();
        
        log.info("Server connection manager shutdown completed");
    }
    
    /**
     * Heartbeat message
     */
    public static class HeartbeatMessage {
        private final long timestamp;
        
        public HeartbeatMessage(long timestamp) {
            this.timestamp = timestamp;
        }
        
        public long getTimestamp() {
            return timestamp;
        }
    }
    
    /**
     * Server connection handler
     */
    private class ServerConnectionHandler extends SimpleChannelInboundHandler<Object> {
        private final ServerType serverType;
        
        public ServerConnectionHandler(ServerType serverType) {
            this.serverType = serverType;
        }
        
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            log.debug("Connection to {} server activated: {}", 
                     serverType.getName(), ctx.channel().remoteAddress());
        }
        
        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            log.debug("Connection to {} server inactive: {}", 
                     serverType.getName(), ctx.channel().remoteAddress());
            handleDisconnection(serverType, ctx.channel());
        }
        
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
            // Handle heartbeat response
            if (msg instanceof HeartbeatMessage) {
                HeartbeatMessage heartbeat = (HeartbeatMessage) msg;
                log.debug("Received heartbeat from {} server: timestamp={}", 
                         serverType.getName(), heartbeat.getTimestamp());
            }
            // Other message handling...
        }
        
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            log.error("Exception in connection to {} server", serverType.getName(), cause);
            ctx.close();
        }
    }
    
    /**
     * Establish connection to a server
     * @param serverType Server type
     * @return CompletableFuture<Channel>
     */
    public CompletableFuture<Channel> establishConnection(ServerType serverType) {
        return connectToServer(serverType);
    }
    
    /**
     * Shutdown all connections
     */
    public void shutdownAll() {
        log.info("Shutting down all server connections...");
        
        // Close all connection pools
        for (ConnectionPool pool : connectionPools.values()) {
            pool.closeAll();
        }
        
        // Stop heartbeat detection
        stopHeartbeat();
        
        // Shutdown event loop
        try {
            workerGroup.shutdownGracefully(5, 10, TimeUnit.SECONDS).sync();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("Interrupted while shutting down worker group", e);
        }
        
        log.info("All server connections shut down");
    }
    
    /**
     * Extension: closeAll method for ConnectionPool
     */
    public static class ConnectionPoolExt {
        public static void closeAll(ConnectionPool pool) {
            Channel channel;
            while ((channel = pool.getConnection()) != null) {
                channel.close();
            }
        }
    }
    
    /**
     * Remove client connection by channel ID
     * @param channelId Channel ID
     */
    public void removeClientConnection(String channelId) {
        // Remove from client connection tracking (if implemented)
        log.debug("Removing client connection: {}", channelId);
    }
    
    /**
     * Register with central server
     * @param serverType Server type
     * @param host Host address
     * @param port Port number
     */
    public void registerWithCentralServer(Enum<?> serverType, String host, int port) {
        log.info("Registering {} with central server at {}:{}", serverType, host, port);
        // Implementation for registering with central server
    }
    
    /**
     * Unregister from central server
     * @param serverType Server type
     */
    public void unregisterFromCentralServer(Enum<?> serverType) {
        log.info("Unregistering {} from central server", serverType);
        // Implementation for unregistering from central server
    }
}